using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Html;
using System.Runtime.CompilerServices;
using System.Serialization;
using Awaiter;
using Nsb;
using Nsb.Classes;
using ThemeChain;
using Yoshi.Classes;
using jQueryApi;

namespace WhirliGig.Classes
{
    [IgnoreNamespace]
    public class GigShow : PkgBase
    {
        protected const int SlideFade = 200;
        public static string PageSelectTo;
        public static jQueryObject PageSelectToEl;
        public static GigShow Instance;
        public static readonly RegularExpression IsChildGigRx = new RegularExpression(@"^\s*""?gigs""?\s*:", "");

        #region Configuration

        public static readonly Dictionary<string, GigShowConfig> CssMap =
            new Dictionary(
                "Button", new Dictionary("inner", "button", "rounding", false, "css", true, "doubleclick", true),
                "TextBox", new Dictionary("inner", "input[type=text]", "rounding", true, "css", true, "doubleclick", true),
                "TextArea", new Dictionary("inner", "textarea", "rounding", true, "css", true, "doubleclick", true),
                "TextItem", new Dictionary("inner", Gig.DotGigContentClassName, "rounding", true, "css", false, "doubleclick", true),
                "DropImage", new Dictionary("inner", "img", "rounding", true, "css", false, "doubleclick", false)
                );

        #endregion Configuration

        #region Finders

        public SlideData CurrentSlide
        {
            get { return Slides[Slide]; }
        }

        public static Dictionary<string, GigData> CurrentGigs
        {
            get { return Instance.CurrentSlide.gigs; }
        }

        public static jQueryObject GigElById(string id)
        {
            return jQuery.Select(id.Substr(0, 1) == "#" ? id : "#" + id, PageSelectToEl);
        }

        public static jQueryObject GigElBySelect(string sel)
        {
            return jQuery.Select(sel, PageSelectToEl);
        }

        public GigData GigById(string id)
        {
            if (id != null)
                return CurrentSlide.gigs[id];
            return null;
        }

        public static GigData GigByEl(jQueryObject el)
        {
            return Instance.GigById(el.GetAttribute("id"));
        }

        public static jQueryObject FindInGroup(string gid, string selector)
        {
            foreach (KeyValuePair<string, GigData> p in Instance.CurrentSlide.gigs)
            {
                if (p.Value.groupId == gid)
                {
                    jQueryObject sel = GigElById(p.Value.id);
                    if (sel.Is(selector))
                        return sel;
                    sel = sel.Find(selector);
                    if (sel.Length > 0)
                        return sel;
                }
            }
            return (jQueryObject) Script.Literal("jQuery()");
        }

        public object GetActionById(string id)
        {
            GigData gd = GigById(id);
            return null;
        }

        #endregion Finders

        //public const string EditingKey = "GigEditing";
        public bool Curating;
        public int DefaultInterval = 5000;
        public bool Editing;
        public IGigShowEdit Editor;
        protected string LastSaved;
        public string PageCssTo;
        public string PartitionKey;
        public string RowKey;
        public int Slide;
        public jQueryObject SlideControls;
        public List<SlideData> Slides = new List<SlideData>();
        protected bool _hasSelect;
        protected Dictionary _opts;
        protected int _playTmr;

        public string BgImgName
        {
            get { return "background.jpg"; }
        }

        public void InitializeAw(Await awp, Dictionary opt)
        {
            Instance = this;
            _opts = opt;
            PartitionKey = (string) opt["PartitionKey"];
            RowKey = (string) opt["RowKey"];
            PageSelectTo = (string) opt["PageSelectTo"];
            PageSelectToEl = jQuery.Select(PageSelectTo);
            PageCssTo = (string) opt["PageCssTo"];
            SlideControls = (jQueryObject) opt["SlideControls"];
            _hasSelect = Script.Value((bool) opt["HasSelect"], false);
            ThemeBase.ActivePage = ThemeYoshi.CurrentPage.Admin;
            new Await()
                .LoadCss(Uri.Scripts("Css/WhirliGig.css"))
                .Commit(awp);
            if (Editing)
                Editor = (IGigShowEdit) this;
        }

        public virtual void Destroy()
        {
            Window.ClearInterval(_playTmr);
        }

        public void RedrawPageGear()
        {
            if (Editor != null)
                Editor.PageMenu();
        }

        public void LoadAw(Await awp)
        {
            Debug.Assert(RowKey != null, "rowKey != null");
            ThemeYoshi.PageDone = false;
            Await awx = new Await()
                .WaitDx(delegate { return jQuery.Select(PageSelectTo).Length > 0; })
                .AddAw(RetrieveBlobStringAw, PartitionKey, RowKey)
                .HandleDl(Exceptions.BlobLoadError,
                          delegate(Await aw)
                          {
                              Slides = new List<SlideData>();
                              Slides.Add(BlankSlide());
                              RedrawPageGear();
                              new Await()
                                  .AddAw(RestoreSlideAw, 0)
                                  .AddDx(delegate { Await.FinishAw(aw); })
                                  .Commit();
                              ThemeYoshi.PageDone = true; // So that at least Listen() keys work
                              PkgResult pr = (PkgResult) aw["Data"];
                              if (pr.result == false && pr.inactive != true && pr.msg == "invalid credentials")
                              {
                                  Inform.Debug("Asking for credentials");
                                  jQuery.Document.Trigger("AskCredentials");
                              }
                          })
                .AddDl(
                    delegate(Await aw)
                    {
                        PageSelectToEl = jQuery.Select(PageSelectTo);
                        PageSelectToEl.AddClass("Slides");
                        string email = (string) Type.GetField(aw["Data"], "email");
                        if (!String.IsNullOrEmpty(email))
                        {
                            ThemeYoshi.Email = email;
                            ThemeYoshi.UserId = (string) Type.GetField(aw["Data"], "userid");
                        }
                        LastSaved = (string) aw[ResultRx];
                        if (Editor != null)
                            Editor.UndoStart(LastSaved, Slide);
                        Slides = (List<SlideData>) Json.Parse(LastSaved);

                        // Normalize each slide
                        foreach (SlideData sd in Slides)
                        {
                            Normalize(sd);
                        }

                        RedrawPageGear();
                        if (ThemeYoshi.CurrentPage.Admin)
                        {
                            Editing = false;
                            Editor = null;
                        }
                        // Kludge
                        jQuery.Select(PageCssTo).Show(); // Forces showing of page for debugging
                        RestoreSlideAw(aw, 0);
                    })
                .AddDx(
                    delegate
                    {
                        if (ThemeBase.Publishing)
                            jQuery.Select(PageCssTo).Show();
                        else
                            jQuery.Select(PageCssTo).FadeIn(300);
                    })
                ;
            //DoGigsCl(awx, CurrentSlide.gigs);
            awx
                .AddFn(FixBodyBg)
                .AddDx(delegate { ThemeYoshi.PageDone = true; })
                .Commit(awp);
        }

        public static SlideData BlankSlide()
        {
            SlideData sd = new SlideData();
            sd.gigs = new Dictionary<string, GigData>();
            sd.css = ThemeYoshi.DefaultPageCss;
            return sd;
        }

        public void RestoreSlideAw(Await awp, int slide)
        {
            Slide = slide;
            PageSelectToEl.Empty();
            jQuery.Select("head style").Remove();
            if (!String.IsNullOrEmpty(CurrentSlide.style))
                jQuery.FromHtml("<style/>").Append(CurrentSlide.style).AppendTo("head");
            if (CurrentSlide.gigs != null)
            {
                int interval = CurrentSlide.interval;
                CurrentSlide.interval = Type.GetScriptType(interval) == "undefined" || interval < 1000 ? DefaultInterval : interval;
                if (!String.IsNullOrEmpty(PageCssTo))
                {
                    Dictionary css2 = (Dictionary) jQuery.Extend(new Dictionary(), CurrentSlide.css);
                    if (CurrentSlide.hasBackground)
                        css2["background-image"] = ThemeYoshi.BgImgUrl;
                    jQuery.Select(PageCssTo).CSS(css2);
                    SetBackgroundColor((string) css2["background-color"]);
                    //Inform.Dump(css2, "RestoreSlide css2");
                }
                // Add the page edge rulers
                if (Editor != null && !ThemeYoshi.CurrentPage.Admin)
                    jQuery.Select("#PageContent").AddClass("PageEdge");
                Await awx = new Await();
                awx.AddAw(LoadActionAw);
                InjectGigsCl(awx, CurrentSlide.gigs);
                awx.AddFn(SetMinPageHeight);
                DoGigsCl(awx, CurrentSlide.gigs);
                awx.AddFn(BuildAction);
                awx.Commit(awp);
            }
            else
            {
                SetMinPageHeight();
                awp.Done();
            }
        }

        public void FixBodyBg()
        {
            jQuery.FromObject(Document.Body).Height(jQuery.Document.GetHeight());
        }

        public void SetBackgroundColor(string c)
        {
            //
            // Best I can do until I can figure out how to determine the default background color.
            // Otherwise partial colors will mix differently with the default color if it is not white.
            //
            //Script.Literal("$('body').height($(document).height())");
            jQuery.Select(PageCssTo).CSS("background-color", "");
            jQuery.Select("html").CSS(new Dictionary("background-color", ""));
            jQuery.FromObject(Document.Body).CSS(new Dictionary("background-color", ""));
            if (Css.IsPartialColor(c))
            {
                Color hb = Css.GetColor(jQuery.Select("html").GetCSS("background-color"));
                //Inform.Debug("html bg color {0}", jQuery.Select("html").GetCSS("background-color"));
                if (hb.red != 255 || hb.green != 255 || hb.blue != 255)
                    jQuery.Select("html").CSS(new Dictionary("background-color", "#FFF"));
            }
            jQuery.FromObject(Document.Body).CSS(new Dictionary("background-color", c));
            FixBodyBg();
        }

        private void InjectGigsCl(Await awp, Dictionary<string, GigData> gigs)
        {
            foreach (KeyValuePair<string, GigData> p in gigs)
            {
                RestoreCl(awp, p.Value);
            }
        }

        private void RestoreCl(Await awp, GigData gd)
        {
            awp.AddDx(delegate { InjectIntoPage(gd, Editing && _playTmr == 0 ? Editor.NewGigEdit(this) : new Gig()); });
        }

        private void DoGigsCl(Await awp, Dictionary<string, GigData> gigs)
        {
            foreach (KeyValuePair<string, GigData> p in gigs)
            {
                DoGigCl(awp, p.Value);
            }
        }

        private void DoGigCl(Await awp, GigData gd)
        {
            if (!String.IsNullOrEmpty(gd.load))
            {
                string[] scripts = gd.load.Split(",");
                foreach (string s in scripts)
                    awp.LoadScript(s);
            }

            if (!String.IsNullOrEmpty(gd.build))
                awp.AddDx(
                    delegate { DoBuildAction((Gig) GigElById(gd.id).GetDataValue(Gig.GigClassName), gd); });
        }

        private static void FixGd(GigData gd)
        {
            gd.css = gd.css ?? new Dictionary();
            gd.floated = Script.IsValue(gd.floated) && gd.floated;
            gd.embedded = Script.IsValue(gd.embedded) && gd.embedded;
        }

        /// <summary>
        /// Workhorse builds passed Gig into a jQueryObect and inserts it into the page.
        /// </summary>
        /// <param name="gd">Gig data</param>
        /// <param name="gg">Current gig object</param>
        /// <param name="parent">Optional parent to insert gig inside</param>
        /// <returns></returns>
        public Gig InjectIntoPage(GigData gd, Gig gg)
        {
            gg.SetId(gd.id);
            FixGd(gd);
            CurrentSlide.gigs[gd.id] = gd;
            jQueryObject where = PageSelectToEl;
            if (where.Length != 0)
            {
                jQueryObject content = Gig.ParseContent(gd.html);
                gg.FromElement(content)
                    .AddClass(gd.classNames)
                    .AddAttr(gd.attributes)
                    .AddType(gd.type)
                    .AddHasHeight(gd.hasHeight)
                    ;
                jQueryObject iel = gg.Element.Find(Gig.DotGigContentClassName);
                if (iel.Length > 0)
                {
                    if (!String.IsNullOrEmpty(gd.iid))
                        iel.Attribute("id", gd.iid);
                    if (!String.IsNullOrEmpty(gd.iclass))
                        iel.AddClass(gd.iclass);
                }
                PlaceGig(gg, where, gd);
                gg.Element.CSS(new Dictionary("position", "absolute", "top", gd.pos.Top, "left", gd.pos.Left, "width", gd.dim.width));
                if (gd.hasHeight)
                    gg.Element.CSS(new Dictionary("height", gd.dim.height));
                Dictionary css = gd.css;
                if (Editor != null)
                    css = Editor.Configure(gd, gg);
                MapCss(gd, gg.Element, css);
                gg.AddTilt(gd.tilt);
                if (Editor != null)
                    Editor.MakeDraggable(gg);
                if (gd.selecTo != PageSelectTo)
                    gg.Element.Hide();
            }
            else
            {
                throw new Exception(Exceptions.Format(Exceptions.MissingDomElement, Exceptions.Fn(Script.Literal("arguments"))));
            }
            return gg;
        }

        /// <summary>
        /// Apply css to gig andding other css stuff too (needs better doc)
        /// </summary>
        /// <param name="gd">gig data</param>
        /// <param name="el">page element</param>
        /// <param name="css">css to apply</param>
        public static void MapCss(GigData gd, jQueryObject el, Dictionary css)
        {
            GigShowConfig opt = CssMap[gd.type];
            if (opt == null)
            {
                el.CSS(css);
                return;
            }
            jQueryObject sel = jQuery.Select(opt.inner, el);
            if (sel.Length > 0)
            {
                int br = Number.ParseInt((gd.css["border-radius"] ?? 0).ToString());
                int bw = Css.BorderWidth(gd.css);
                br = Math.Max(br - bw, 0);
                if (opt.rounding)
                {
                    sel
                        .CSS(
                            new Dictionary(
                                "-moz-border-radius", br,
                                "-webkit-border-radius", br,
                                "border-radius", br
                                ));
                }
                if (opt.css)
                {
                    int wd = Css.PaddingHorz(gd.css) + (bw*2);
                    int ht = Css.PaddingVert(gd.css) + (bw*2);
                    sel.CSS(css);
                    sel.CSS(new Dictionary("width", gd.dim.width - wd, "height", gd.dim.height - ht /*, "top", -bw, "left", -bw*/));
                }
                else
                    el.CSS(css);
            }
            else
            {
                el.CSS(css);
            }
        }

        public void PlaceGig(Gig gg, jQueryObject deef, GigData gd)
        {
            string aa = null;
            GigData gp = GigById(gd.parentId);
            if (gp != null)
                aa = gp.actions.place;

            if (aa != null)
                DoAction(aa, gg, gd);
            else
                gg.AppendTo(deef);
        }

        public void DoAction(string action, object arg1, object arg2)
        {
            try
            {
                Action<object, object> fn = (Action<object, object>)Script.Eval(action.Trim());
                fn(arg1, arg2);
            }
            catch (Exception ex)
            {
                Inform.Error("Do action error: {0}: {1}", action, ex);
            }
        }

        public void DoBuildAction(Gig gg, GigData gd)
        {
            if (!String.IsNullOrEmpty(gd.build) && !ThemeBase.Publishing)
            {
                Window.SetTimeout(
                    delegate
                    {
                        string[] actions = gd.build.Split(",");
                        foreach (string act in actions)
                        {
                            DoAction(act, gg, gd);
                        }
                    }, 1);
            }
        }

        public void LoadActionAw(Await awp)
        {
            new Await()
                .AddDl(
                    delegate(Await aw)
                    {
                        ThemeYoshi.CurrentLoadCss = CurrentSlide.loadCss;
                        StaticBuild.LoadCssAw(aw, CurrentSlide.loadCss);
                    })
                .AddDl(
                    delegate(Await aw)
                    {
                        ThemeYoshi.CurrentLoadLibs = CurrentSlide.load;
                        StaticBuild.LoadLibsAw(aw, CurrentSlide.load);
                    })
                .Commit(awp);
        }

        public void BuildAction()
        {
            SlideData sd = CurrentSlide;
            ThemeYoshi.CurrentBuildAction = sd.build; // Record it for publisher to grab when building published page

            Window.SetTimeout(
                delegate
                {
                    if (sd.curate != null && !ThemeBase.Publishing)
                    {
                        string[] actions = sd.curate.Split(",");
                        foreach (string act in actions)
                        {
                            try
                            {
                                Script.Eval(act.Trim() + "()");
                            }
                            catch (Exception ex)
                            {
                                Inform.Error("Slide curate error: {0}: {1}", act, ex);
                            }
                        }
                    }
                    if (sd.build != null && !ThemeBase.Publishing && ThemeBase.ActivePage)
                    {
                        string[] actions = sd.build.Split(",");
                        foreach (string act in actions)
                        {
                            try
                            {
                                Script.Eval(act.Trim() + "()");
                            }
                            catch (Exception ex)
                            {
                                Inform.Error("Slide build error: {0}: {1}", act, ex);
                            }
                        }
                    }
                    string pgAction = (string) Storage.GetSession(ThemeBase.PageActionKey);
                    if (!String.IsNullOrEmpty(pgAction))
                    {
                        Storage.RemoveSession(ThemeBase.PageActionKey);
                        Window.SetTimeout(delegate { Script.Eval(pgAction); }, 13);
                    }
                }, 1);
        }

        public void ChangeSlideEv(jQueryEvent e)
        {
            RestoreSlideAw(Await.AsyncAw, Number.ParseInt(jQuery.FromElement(e.Target).GetValue()));
            StopSlidesEv(e);
        }

        public void NextSlide()
        {
            PageSelectToEl.Children().FadeOut(SlideFade);
            new Await()
                .Sleep(SlideFade)
                .AddDx(
                    delegate
                    {
                        RestoreSlideAw(Await.AsyncAw, Slide + 1 >= Slides.Count ? 0 : Slide + 1);
                        jQuery.Select("select", SlideControls).Value(Slide.ToString());
                    })
                .Commit();
        }

        public void NextSlideEv(jQueryEvent e)
        {
            RestoreSlideAw(Await.AsyncAw, Slide + 1 >= Slides.Count ? 0 : Slide + 1);
            jQuery.Select("select", SlideControls).Value(Slide.ToString());
            StopSlidesEv(e);
            DomElement.CancelEvent(e);
        }

        public void PrevSlideEv(jQueryEvent e)
        {
            RestoreSlideAw(Await.AsyncAw, Slide - 1 < 0 ? Slides.Count - 1 : Slide - 1);
            jQuery.Select("select", SlideControls).Value(Slide.ToString());
            StopSlidesEv(e);
            DomElement.CancelEvent(e);
        }

        public void AddSlideEv(jQueryEvent e)
        {
            StopSlidesEv(e);
            new Await()
                .AddDx(
                    delegate
                    {
                        SlideData slide = new SlideData();
                        slide.slide = Slide = Slides.Count;
                        slide.gigs = new Dictionary<string, GigData>();

                        Slides.Add(slide);
                        new Await()
                            .AddAw(RestoreSlideAw, Slide)
                            .AddDl(
                                delegate(Await aw)
                                {
                                    RedrawPageGear();
                                    IGigShowEdit ths = (IGigShowEdit) this;
                                    ths.SaveAllAw(aw);
                                })
                            .Commit();
                    })
                .Commit();
        }

        public void RemSlideEv(jQueryEvent e)
        {
            StopSlidesEv(e);
            new Await()
                .AddAw(new Ask().OkCancelAw, ThemeWhirliGig.DeleteMsg)
                .AddAw(DomElement.RequireResultAw, true)
                .AddDx(
                    delegate
                    {
                        Slides.RemoveAt(Slide);
                        if (Slides.Count == 0)
                            Slides.Add(BlankSlide());
                        int cnt = 0;
                        foreach (SlideData sd in Slides)
                        {
                            sd.slide = cnt++;
                        }
                        new Await()
                            .AddAw(RestoreSlideAw, Math.Max(Slide - 1, 0))
                            .AddDl(
                                delegate(Await aw)
                                {
                                    RedrawPageGear();
                                    IGigShowEdit ths = (IGigShowEdit) this;
                                    ths.SaveAllAw(aw);
                                })
                            .Commit();
                    })
                .Commit();
        }

        public void PlaySlidesEv(jQueryEvent e)
        {
            Window.ClearInterval(_playTmr);
            _playTmr = Window.SetInterval(NextSlide, CurrentSlide.interval);
            RestoreSlideAw(Await.AsyncAw, Slide);
            RedrawPageGear();
        }

        public void StopSlidesEv(jQueryEvent e)
        {
            if (_playTmr > 0)
            {
                Window.ClearInterval(_playTmr);
                _playTmr = 0;
                RestoreSlideAw(Await.AsyncAw, Slide);
            }
            RedrawPageGear();
        }

        public void Normalize(SlideData sd)
        {
            sd.css = (Dictionary) jQuery.Extend(new Dictionary(), ThemeYoshi.DefaultPageCss, sd.css);
            foreach (KeyValuePair<string, GigData> p in sd.gigs)
            {
                if (p.Value.type == "TextItem")
                    p.Value.html = p.Value.html.Replace("<p>", "<div class='GigC'>").Replace("</p>", "</div>");
                if (p.Value.actions == null)
                    p.Value.actions = new GigActions();
            }
        }

        public static Dictionary GetTilts()
        {
            Dictionary tilts = new Dictionary();
            foreach (KeyValuePair<string, GigData> gigs in Instance.CurrentSlide.gigs)
            {
                GigData gd = gigs.Value;
                if (Type.HasField(gd, "tilt") && gd.tilt != 0)
                    tilts[gd.id] = gd.tilt;
            }
            return tilts;
        }

        public void SetMinPageHeight()
        {
            int pageEnd = 0;
            foreach (SlideData sd in Slides)
            {
                if (sd.gigs != null && sd.gigs.Count > 0)
                    foreach (KeyValuePair<string, GigData> p in sd.gigs)
                    {
                        if (!p.Value.floated)
                            // GigDataOffFix - needs to iterate on actual Gigs and get offset from element
                            pageEnd = Math.Max(GigElById(p.Value.id).GetOffset().Top + p.Value.dim.height, pageEnd);
                    }
            }
            jQueryObject el = PageSelectToEl;
            int mh = pageEnd - el.GetOffset().Top - 10;
            if (mh < 0) mh = 0;
            el.Height(mh);
        }

        public void CuratingOff()
        {
            Storage.SetLocal(ThemeYoshi.CuratingKey, false);
            ThemeYoshi.ReloadPage();
        }

        [PreserveName, PreserveCase]
        public static void PublishPagePx(int nxt)
        {
            new Await()
                .LoadScript(Uri.Scripts("Scripts/WhirliGigEdit.js"))
                .AddDx(delegate { Script.Literal("Publish.PublishPage({0})", nxt); })
                .Commit();
        }

        [PreserveName, PreserveCase]
        public static void PublishThisPagePx()
        {
            new Await()
                .LoadScript(Uri.Scripts("Scripts/WhirliGigEdit.js"))
                .AddDx(delegate { Script.Literal("Publish.PublishThisPage()"); })
                .Commit();
        }
    }
}